#include "Httper.h"
#include <QEventLoop>
#include "../Utility/XXlogger.h"
#include <QUrlQuery>
#include "../XXqtDefine.h"

#include <mutex>
#include <QHttpMultiPart>

Httper::Httper(const QString &host, QObject *parent) : QObject(parent)
{
    _host = host;
    _isCanceled = false;
    _reply = nullptr;
    _eventBlock = nullptr;
    _resultBlock = nullptr;
    _progressBlock = nullptr;
    _timer = nullptr;
    connect(this,&Httper::sigResult,this,&Httper::onResult,Qt::QueuedConnection);
}
Httper::~Httper(){
    cancel();
}
bool Httper::isRunning(){
    return nullptr!=_reply && _reply->isRunning();
}
void Httper::cancel(){
    if(!isRunning()){
        return;
    }
    xxInfoStr(QString("cancel. name:%1").arg(_name));
    _isCanceled = true;
    stopTimer();
    if(nullptr != _reply){
        disconnect(_reply,&QNetworkReply::finished,this,&Httper::reply_onFinished);
        _reply->close();
        _reply = nullptr;
    }
}
bool Httper::post(const QString &api,
                  const QMap<QString,QString> &param,
                  const QMap<QString,QString> &header,
                  const QVariant &body,
                  std::function<void(QString,QVariant)> resultBlock,
                  int timeout){
    return post(_host,api,param,header,body,resultBlock,timeout);
}
bool Httper::post(const QString &host,
                  const QString &api,
                  const QMap<QString,QString> &param,
                  const QMap<QString,QString> &header,
                  const QVariant &body,
                  std::function<void(QString,QVariant)> resultBlock,
                  int timeout){
    // set url param
    QUrl url(QString("%1%2").arg(host).arg(api));
    if(!param.isEmpty()){
        QUrlQuery urlQuery;
        for (auto iter=param.cbegin(); iter!=param.cend(); iter++) {
            urlQuery.addQueryItem(iter.key(),iter.value());
        }
        url.setQuery(urlQuery);
    }
    return post(url,header,body,resultBlock,timeout);
}
bool Httper::post(const QUrl &url,
                  const QMap<QString,QString> &header,
                  const QVariant &body,
                  std::function<void(QString,QVariant)> resultBlock,
                  int timeout){
    if(isRunning()){
        xxLog("could not post when running.");
        return false;
    }
    if(resultBlock == nullptr){
        xxLog("invalid block.");
        return false;
    }
    _resultBlock = resultBlock;
    _isCanceled = false;

    // set url param
    _request = QNetworkRequest(url);
    if(!header.isEmpty()){
        for (auto iter=header.constBegin(); iter!=header.constEnd(); iter++) {
            _request.setRawHeader(iter.key().toUtf8(),iter.value().toUtf8());
        }
    }
    else{
        _request.setRawHeader("Content-Type","application/json; charset=utf-8");
    }
    _request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    if(_eventBlock){
        _request.setRawHeader(QByteArray("Accept"), QByteArray("*/*"));
        _request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
        _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); // Events shouldn't be cached
        _bufferForRead.clear();
    }

    // set post body
    QHttpMultiPart *multiPart = nullptr;
    QByteArray rawPart;
    if(body.isValid()){
        if(body.canConvert<QVariantMap>()){
            multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType,this);
            QVariantMap map = body.toMap();
            for(auto iter=map.begin(); iter!=map.end(); iter++){
                QString key = iter.key();
                QString type,content;
                if(iter.value().canConvert<QVariantMap>()){
                    QVariantMap ca = iter.value().toMap();
                    type = ca["type"].toString();
                    content = ca["content"].toString();
                }
                else{
                    type="";
                    content = iter.value().toString();
                }

                QHttpPart part;
                if(!type.isEmpty()) part.setHeader(QNetworkRequest::ContentTypeHeader,type);
                part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"" + key + "\""));
                part.setBody(content.toUtf8());
                multiPart->append(part);
            }
        }
        else if(body.canConvert<QString>()){
            rawPart = body.toString().toUtf8();
        }
        else{

        }
    }


    if(timeout>0){
        xxLog("post and start timeout timer.");
        startTimer(timeout);
    }
    else{
        xxLog("post without timeout timer.");
    }

    _type = POST;
    if(multiPart){
        _reply = _manager.post(_request,multiPart);
    }
    else{
        _reply = _manager.post(_request,rawPart);
    }
    connect(_reply, &QNetworkReply::finished, this, &Httper::reply_onFinished,Qt::QueuedConnection);
    if(_eventBlock){
        connect(_reply, &QNetworkReply::readyRead, this, &Httper::reply_onReadReadyForEvent,Qt::QueuedConnection);
    }
    return true;
}

bool Httper::get(const QString &api, const QMap<QString,QString> &param, const QMap<QString,QString> &header,std::function<void(QString,QVariant)> resultBlock, int timeout){
    return get(_host,api,param,header,resultBlock,timeout);
}
bool Httper::get(const QString &host, const QString &api, const QMap<QString,QString> &param, const QMap<QString,QString> &header,std::function<void(QString,QVariant)> resultBlock, int timeout){
    // set url param
    QUrl url(QString("%1%2").arg(host).arg(api));
    if(!param.isEmpty()){
        QUrlQuery urlQuery;
        for (auto iter=param.cbegin(); iter!=param.cend(); iter++) {
            urlQuery.addQueryItem(iter.key(),iter.value());
        }
        url.setQuery(urlQuery);
    }
    return get(url,header,resultBlock,timeout);
}
bool Httper::get(const QUrl &url, const QMap<QString,QString> &header,std::function<void(QString,QVariant)> resultBlock, int timeout){
    if(isRunning()){
        xxLog("could not get when running.");
        return false;
    }
    if(resultBlock == nullptr){
        xxLog("invalid block.");
        return false;
    }
    _resultBlock = resultBlock;
    _isCanceled = false;

    // set url param
    _request = QNetworkRequest(url);
    if(!header.isEmpty()){
        for (auto iter=header.constBegin(); iter!=header.constEnd(); iter++) {
            _request.setRawHeader(iter.key().toUtf8(),iter.value().toUtf8());
        }
    }
    if(_progressBlock){
        _bufferForRead.clear();
    }

    if(timeout>0){
        xxLog("get and start timeout timer.");
        startTimer(timeout);
    }
    else{
        xxLog("get without timeout timer.");
    }

    _type = GET;
    _reply = _manager.get(_request);
    connect(_reply, &QNetworkReply::finished, this, &Httper::reply_onFinished,Qt::QueuedConnection);
    if(_progressBlock){
        connect(_reply, &QNetworkReply::readyRead, this, &Httper::reply_onReadReadyForProgress);
        connect(_reply, &QNetworkReply::downloadProgress, this, &Httper::reply_onDownloadProgress);
    }
    return true;
}

bool Httper::uploadFile(const QUrl &url, const QString &filePath, const QVariantMap &formData, std::function<void(QString,QVariant)> resultBlock, int timeout){
    uploadFile(url,filePath,"file",formData,resultBlock,timeout);
    return true;
}
void Httper::uploadFile(const QUrl &url, const QString &filePath, const QString &fileKey, const QVariantMap &formData, std::function<void(QString,QVariant)> resultBlock, int timeout, QString name){
    // env check
    _name = name;
    if(resultBlock == nullptr){
        xxErrStr(QString("invalid block. name:%1").arg(name));
        return;
    }
    _resultBlock = resultBlock;
    if(isRunning()){
        xxErrStr(QString("could not get when running. name:%1").arg(name));
        emit sigResult("could not get when running.",QVariant());
        return;
    }


    // open file
    QFile *file = new QFile(filePath);
    if(!file->open(QIODevice::ReadOnly)){
        delete file;
        xxErrStr(QString("failure to open file. name:%1 path:%2").arg(name).arg(filePath));
        emit sigResult("failure to open file. path:"+filePath,QVariant());
        return;
    }
    _isCanceled = false;

    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    // set file to body
    QHttpPart filePart;
    filePart.setHeader(QNetworkRequest::ContentTypeHeader, suffixNameToApplicationType(QFileInfo(filePath).completeBaseName()));
    filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\""+fileKey+"\"; filename=\"" + QFileInfo(filePath).fileName() + "\""));
    //filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\""+fileKey+"\"; filename=\"" + file->fileName() + "\""));
    filePart.setBodyDevice(file);
    file->setParent(multiPart);
    multiPart->append(filePart);

    // set form data to body
    for(auto iter=formData.begin(); iter!=formData.end(); iter++){
        QString key = iter.key();
        QString type,content;
        if(iter.value().canConvert<QVariantMap>()){
            QVariantMap ca = iter.value().toMap();
            type = ca["type"].toString();
            content = ca["content"].toString();
        }
        else{
            type="";
            content = iter.value().toString();
        }

        QHttpPart part;
        if(!type.isEmpty()) part.setHeader(QNetworkRequest::ContentTypeHeader,type);
        part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"" + key + "\""));
        part.setBody(content.toUtf8());
        multiPart->append(part);
    }

    // set url param
    _request = QNetworkRequest(url);
    _request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    if(_eventBlock){
        _request.setRawHeader(QByteArray("Accept"), QByteArray("*/*"));
        _request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
        _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); // Events shouldn't be cached
        _bufferForRead.clear();
    }

    if(timeout>0){
        xxInfoStr(QString("upload file and start timeout timer. name:%1").arg(name));
        startTimer(timeout);
    }
    else{
        xxInfoStr(QString("upload file without timeout timer. name:%1").arg(name));
    }

    _type = UPLOADFILE;
    _reply = _manager.post(_request,multiPart);
    connect(_reply, &QNetworkReply::finished, this, &Httper::reply_onFinished,Qt::QueuedConnection);
    if(_eventBlock){
        connect(_reply, &QNetworkReply::readyRead, this, &Httper::reply_onReadReadyForEvent,Qt::QueuedConnection);
    }
}

void Httper::timer_onTimeout(){
    xxLog("timeout.")
    if(_isCanceled || nullptr == _resultBlock){
        xxLog("return because cancel or block is nullptr. cancel:%d nullptr:%d",_isCanceled,nullptr == _resultBlock);
        return;
    }
    cancel();
    emit sigResult("timeout",QVariant());
}
void Httper::reply_onFinished(){
    xxInfoStr(QString("replyed. name:%1").arg(_name));
    if(_isCanceled || nullptr == _resultBlock || _reply != sender()){
        xxInfo("return because cancel or block is nullptr. name:%s cancel:%d nullptr:%d",_name.toUtf8().data(),_isCanceled,nullptr == _resultBlock);
        return;
    }

    stopTimer();
    disconnect(_reply,&QNetworkReply::finished,this,&Httper::reply_onFinished);
    disconnect(_reply,&QNetworkReply::readyRead,this,&Httper::reply_onReadReadyForEvent);
    disconnect(_reply,&QNetworkReply::readyRead,this,&Httper::reply_onReadReadyForProgress);
    disconnect(_reply,&QNetworkReply::downloadProgress,this,&Httper::reply_onDownloadProgress);

    auto replyError = _reply->error();
    auto replyBytes = _reply->readAll();
    auto replyErrorString = _reply->errorString();
    _reply->close();
    _reply->deleteLater();
    _reply = nullptr;
    xxInfoStr("reply was deleted. name:"+_name);

    if(QNetworkReply::NoError==replyError || QNetworkReply::OperationCanceledError==replyError){
        // FIXME: 将不会使用QString返回，而是QByteArray
        if(POST == _type){
            QString json = QString::fromUtf8(replyBytes);
            if(json.isEmpty()){
                json = QString::fromUtf8(_bufferForRead);
            }
            emit sigResult("",json);
        }
        else{
            emit sigResult("",replyBytes.isEmpty()?_bufferForRead:replyBytes);
        }
    }
    else{
        emit sigResult(replyErrorString,QVariant());
    }
}
void Httper::reply_onReadReadyForEvent(){
    /*
     * /r/n: sign for kv
     * /r/n/r/n: sign for block
     *
     * /r/n
     * // block begin
     * key:value\r\n
     * key:value\r\n
     * ...
     * \r\n\r\n
     * // block begin
     * key:value\r\n
     * key:value\r\n
     * ...
     * \r\n\r\n
    */
    QByteArray bytes = _reply->readAll();
    qDebug() << bytes;
    _bufferForRead.append(bytes);
    if(!_bufferForRead.contains("\n\n")){
        return;
    }

    int nextPacketIndex = 0;
    while(_bufferForRead.indexOf("\n\n",nextPacketIndex)>=0){
        int lastPacketIndex = nextPacketIndex;
        nextPacketIndex = _bufferForRead.indexOf("\n\n",nextPacketIndex)+2;
        QByteArray packet = _bufferForRead.mid(lastPacketIndex,nextPacketIndex-lastPacketIndex);

        QVariantMap info;
        auto pairs = packet.split('\n');
        foreach(auto pair, pairs){
            if(pair.isEmpty()){
                continue;
            }
            int splitIndex = pair.indexOf(':');
            if(splitIndex<=0){
                continue;
            }

            QString key = QString::fromUtf8(pair.left(splitIndex));
            QString value = QString::fromUtf8(pair.mid(splitIndex+1));
            if(key.isEmpty() || value.isEmpty()){
                continue;
            }
            info[key] = value;
        }

        if(_eventBlock){
            _eventBlock(QString::fromUtf8(packet),info);
        }
    }

    if(nextPacketIndex>0){
        _bufferForRead = _bufferForRead.mid(nextPacketIndex);
    }
}
void Httper::reply_onReadReadyForProgress(){
    if(_isCanceled){
        return;
    }
    _bufferForRead += _reply->readAll();
}
void Httper::reply_onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal){
    if(_isCanceled || !_progressBlock){
        return;
    }
    _progressBlock(bytesReceived,bytesTotal);
}
void Httper::onResult(QString errorString, QVariant response){
    if(_isCanceled || nullptr == _resultBlock){
        return;
    }
    _resultBlock(errorString,response);
}

void Httper::startTimer(int ms){
    if(nullptr!=_timer){
        _timer->stop();
        delete  _timer;
    }
    _timer = new QTimer;
    connect(_timer,&QTimer::timeout,this,&Httper::timer_onTimeout);
    _timer->start(ms);
}
void Httper::stopTimer(){
    if(nullptr!=_timer){
        _timer->stop();
        delete  _timer;
        _timer = nullptr;
    }
}
QString Httper::suffixNameToApplicationType(const QString &suffixName){
    if("zip" == suffixName){
        return "application/zip";
    }
    else{
        return "application/octet-stream";
    }
}
